<?php
/**
 * Created by Chen.
 * author: 1481746137@qq.com
 * Date: 2018/1/31
 * Time: 10:35
 */

namespace app\v2\model;


use apiocr\AipOcr;
use think\cache\driver\Redis;
use think\Model;
use think\Request;
use Qiniu\Auth;
use Qiniu\Storage\UploadManager;


class IdcardOcr extends Model
{
    const APP_KEY = 'XysnsTXpMR171IifU39RXdMkV8Cvj2p4';
    const APP_SECRET = 'u0_8CFMo6WECxJRAeUng4Sx770B9w5dQ';

    const OCR_API = "http://api.faceid.com/faceid/v1/ocridcard";
    const GET_NUMBER = "https://api.megvii.com/faceid/lite/raw/get_random_number";
    const VALIDATE_VIDEO = "https://api.megvii.com/faceid/lite/raw/validate_video";
    const VERIFY = "https://api.megvii.com/faceid/lite/raw/verify";


    public function __construct($data = [])
    {
        parent::__construct($data);
        $config = config();
        $this->SELECT = $config['redis_select']; //17号库
        $this->REDISKEY = $config['project_name'] . ':ocrList:';  //身份证OCR redis KEY

        #错误信息
        $this->faceError = [
            'VIDEO_FACE_NOT_FOUND' => '未找到人脸',
            'VIDEO_LOW_FACE_QUALITY' => '上传的视频中人脸质量太差',
            'VIDEO_INVALID_DURATION' => '上传的视频时长不对',
            'VIDEO_MULTIPLE_FACES' => '上传的视频中有多张人脸',
            'VIDEO_NO_AUDIO' => '请检查视频中语音是否正常',
            'VIDEO_UNSUPPORTED_FORMAT' => '视频格式错误',

            'INVALID_IDCARD_NUMBER' => '身份证号有误',
            'NO_SUCH_ID_NUMBER' => '身份证号码不存在',
            'ID_NUMBER_NAME_NOT_MATCH' => '身份证号码与姓名不匹配',

            'VIDEO_SR_ERROR' => '语音识别结果与要求不符',
            'VIDEO_NOT_SYNCHRONIZED' => '视频中唇语识别错误',
            'VIDEO_FACE_INCONSISTENT' => '视频过程中的人脸不一致'

        ];
    }

    /**
     * 身份证识别测试
     * @author cxr <1481746137@qq.com>
     * @param $url
     * @return array|bool
     */
    public function Identify($img, $uid, $type)
    {

        #先存本地压缩识别
        $base64 = explode(',', $img);
        $raw_img = base64_decode($base64[1]);
        $path = ROOT_PATH . 'custom' . DS . rand(1, 1000) . ".jpg";
        @file_put_contents($path, $raw_img);

//        #压缩图片
//        $percent = 0.8;  #原图压缩，不缩放，但体积大大降低
//        $image = (new \imgcompress($raw_img,$percent))->compressImg();
//        @file_put_contents($path,$image);


        $fields = ['api_key' => SELF::APP_KEY,
            'api_secret' => SELF::APP_SECRET,
            'image' => new \CURLFile($path),
            'legality' => 1 //识别合法性
        ];
        $res = $this->curlPost(SELF::OCR_API, $fields);

        $sourceInfo =$this->ocrInfo($uid);

        if (isset($res['side']) && $type == 'front') {

            #正面
            $info['id_card'] = $res['id_card_number'];
            $info['race'] = $res['race'];
            $info['sex'] = $res['gender'] == '男' ? '1' : '0';
            $info['name'] = $res['name'];
//            $info['birthday'] = join('-',$res['birthday']);
            $info['birthday'] = "{$res['birthday']['year']}-{$res['birthday']['month']}-{$res['birthday']['day']}";
            $info['idcard_address'] = $res['address'];

            if (empty($info['id_card']) || empty($info['name'])) {
                $this->error = '正面识别失败';
                return false;
            }

            #验证正反面是否同一个市县区
            if(!$this->checkRegion($info['idcard_address'],$sourceInfo['back']['issued_by'])){
                $this->error = '提供的身份证信息不一致';
                return false;
            }

            #年龄检查
            $allow = model('Apply')->is_reject_age($info['id_card']);
            if($allow){
                $this->error = '年龄不符合申请条件';
                return false;
            }

        } elseif (isset($res['side']) && $type == 'back') {

            #反面
            $info['issued_by'] = $res['issued_by'];
            $info['valid_date'] = $res['valid_date'];

            if (empty($info['issued_by']) || empty($info['valid_date'])) {
                $this->error = '背面识别失败';
                return false;
            }
//            return $sourceInfo;

            #验证正反面是否同一个市县区
            if(!$this->checkRegion($sourceInfo['front']['idcard_address'],$info['issued_by'])){
                $this->error = '提供的身份证信息不一致';
                return false;
            }

            #验证有效期
            $validate = explode('-', $info['valid_date']);
            $va = str_replace('.','-',$validate[1]);
            $now = strtotime(date('Y-m-d'),time()) ;
            $idcard_validate = strtotime($va);
            if($now > $idcard_validate){
                $this->error = '证件已过期';
                return false;
            }

        } else {
            $this->error = '识别失败';
            return false;
        }

        try {

            #上传七牛临时储存redis
            $qiniu = $this->uploadBase64($img);
            $side = $type == 'front'?'id_card_img1':'id_card_img2';
            $info[$side] = $qiniu;

            #保存身份信息到redis
            $redis = new Redis;
            $redis->select($this->SELECT);
            $key = $this->REDISKEY . $uid;
            $redis->hMset($key, [$type => json_encode($info)]); // 正面OR反面
//            $redis ->set($key,json_encode($info),24*60*60);

        } catch (\Exception $e) {
            $this->error = '保存失败，请重新上传';
            return false;
        }

        return ['info' => $info, 'url' => $qiniu];
    }


    public function checkRegion($address,$issueby){

        if(!empty( $address) && !empty( $issueby)){
            $region = mb_substr($issueby,0,2);
            if(strpos($address,$region) == false){
                return false;
            }

        }
        return true;

    }

    /**
     * 获取语音数字Token
     * @author cxr <1481746137@qq.com>
     * @return array
     */
    public function getNumber()
    {
        $fields = ['api_key' => SELF::APP_KEY,
            'api_secret' => SELF::APP_SECRET,
        ];
        $res = $this->curlPost(SELF::GET_NUMBER, $fields);
        $data = ['number' => $res['random_number'],
            'token_number' => $res['token_random_number']
        ];

        return $data;
    }

    /**
     * 上传活体认证视频返回Token
     * @author cxr <1481746137@qq.com>
     * @param $video
     * @param $token
     * @return array|bool
     */
    public function putVideo($video, $token, $uid)
    {
        $fields = ['api_key' => SELF::APP_KEY,
            'api_secret' => SELF::APP_SECRET,
            'token_random_number' => $token,
            'video' => new \CURLFile($video),
            'return_image' => '1' //决定了是否返回从视频中截取的最佳质量图像
        ];

        $res = $this->curlPost(SELF::VALIDATE_VIDEO, $fields);

        if (!isset($res['token_video'])) {
            $error = $res['error_message'];
            $errormsg = isset($this->faceError[$error])?$this->faceError[$error]:$error;
            $this->error = $errormsg;
            return false;
        }
//        $data = ['token_video' => $res['token_video']];


        #保存活体图像
        if($res['image_best']) {
            $qiniu = $this->uploadBase64($res['image_best']);
            $redis = new Redis;
            $redis->select($this->SELECT);
            $key = $this->REDISKEY . $uid;
            $redis->hMset($key, ['image_best' => $qiniu]); // 活体图像
        }


        #人脸认证
        $authRes = $this->verify($res['token_video'],$uid);
        return $authRes;
    }

    /**
     * 结果验证
     * @param $token
     * @return array|bool
     */
    public function verify($token_video, $uid)
    {
        # 验证接收字段
        /*
        $validate = validate($this->name);
        if (!$validate->check($param)) {
            $this->error = $validate->getError();
            return false;
        }
        */

        #用户信息
        $UserInfo = UserInfo::Get(['uid' => $uid]);

        #检查是否已通过
        if($UserInfo->face_reult == 'PASS'){
            return '已通过,无需重复认证';
        }

        #从redis获取信息
        $infoOcr = $this->ocrInfo($uid);
        $info = $infoOcr['front'] + $infoOcr['back'];   //正反面信息
        $info['id_card_img3'] = $infoOcr['image_best']; //活体图像

        $fields = ['api_key' => SELF::APP_KEY,
            'api_secret' => SELF::APP_SECRET,
            'token' => $token_video,
            'comparison_type' => '1',
            'idcard_name' => $info['name'],
            'idcard_number' => $info['id_card']
        ];

        $res = $this->curlPost(SELF::VERIFY, $fields);

        try {
            if (isset($res['liveness'])) {
                if ($res['liveness']['procedure_validation'] == 'PASSED' && $res['liveness']['face_genuineness'] == 'PASSED') {
                    if ((float)$res['result_faceid']['confidence'] >= (float)$res['result_faceid']['thresholds']['1e-4']) {

                        #验证通过
                        $info['face_reult'] = 'PASS';
//                        $UserInfo->face_reult = 'PASS';
                        $UserInfo->save($info);
                        $faceResult = '身份验证通过';
                    } else {
                        #清除不通过信息
                        $UserInfo->id_card_img1 = '';
                        $UserInfo->id_card_img2 = '';

                        $UserInfo->face_reult = 'FAIL';
                        $UserInfo->save();

                        $this->error = '人脸验证不通过';
                        return false;
                    }
                } else {

//                    #清除不通过信息
//                    $UserInfo->id_card_img1 = '';
//                    $UserInfo->id_card_img2 = '';
//                    $UserInfo->save();

                    $error = $res['liveness']['procedure_validation'];
                    $errormsg = isset($this->faceError[$error])?$this->faceError[$error]:'活体验证不通过';
                    $this->error = $errormsg;
                    $this->Log('活体验证不通过'.$errormsg, $res, $uid.$UserInfo->name);
                    return false;
                }
            } else {
//                #清除不通过信息
//                $UserInfo->id_card_img1 = '';
//                $UserInfo->id_card_img2 = '';
//                $UserInfo->save();

                $error = $res['error_message'];
                $errormsg = isset($this->faceError[$error])?$this->faceError[$error]:$error;
                $this->error = $errormsg;
                $this->Log('人脸验证失败'.$errormsg, $res, $uid.$UserInfo->name);
                return false;
            }
        } catch (\Exception $e) {
            $this->Log("出现异常", $res, $uid.$UserInfo->name);
            $this->error = '错误';
            return false;
        }

        return $faceResult;
    }

    /**
     * 获取临时身份证OCR信息
     * @author cxr <1481746137@qq.com>
     * @param $uid
     * @return mixed
     */
    public function ocrInfo($uid)
    {
        #验证通过 从redis取出信息
        $redis = new Redis;
        $redis->select($this->SELECT);
        $key = $this->REDISKEY . $uid;
        $value = $redis->hGetAll($key);

        isset($value['front']) && $front = json_decode($value['front'],true);
        isset($value['back']) && $back = json_decode($value['back'],true);
        #活体图像
        isset($value['image_best']) && $live = $value['image_best'];

        $info = ['front' => $front, 'back' => $back , 'image_best' => $live];
        return $info;
    }


    public function Log($msg, $detail, $uid = '')
    {
        @file_put_contents(LOGS_DIR_NAME . "face_log.txt", $uid.' 认证时间:' . date('Y-m-d H:i:s', time()) . " 结果:" . $msg . ",详情:" . json_encode($detail) . "\n\n", FILE_APPEND);
    }


    public function uploadBase64($pic)
    {
        $token = $this->getQiniuToken();
        if (!$pic) {
            return resultArray(['error' => "没有获取到图片信息"]);
        }
        $uploadManager = new  UploadManager();

        $type = "jpg";
        $name = date("YmdHis") . rand(1, 1000) . "." . $type;
        $filePath = $pic;

        list($ret, $err) = $uploadManager->putFile($token, $name, $filePath, null, $type, false);
        if ($err) {//上传失败
            return resultArray(['error' => "上传失败"]);
        } else {//成功
            //添加信息到数据库
            $url = "http://upload.yueguangbaika.com/" . $ret['key'];
            return $url;
        }
    }

    /**
     * 生成上传凭证
     * @return string
     */
    private function getQiniuToken()
    {

        $accessKey = 'VILnQPToTZGD5b3fvEw8oFkkdGnlNxvGxQQ-fq-5';
        $secretKey = 'SH3ak_rrYL6Yxq2db1UFb5ibtHF9l2hKDHmtWhdu';
        $auth = new Auth($accessKey, $secretKey);

        // 要上传的空间
        // $bucket = 'w-bjj';
        $bucket = 'qh-yueguangbaika';
        return $auth->uploadToken($bucket);//生成token
    }

    private function curlPost($url, $data)
    {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($curl, CURLOPT_TIMEOUT,20);   //超时时间
        if (!empty($data)) {
            curl_setopt($curl, CURLOPT_POST, 1);
            curl_setopt($curl, CURLOPT_SAFE_UPLOAD, false); // 一定要加否则为空..
            curl_setopt($curl, CURLOPT_BINARYTRANSFER, true); //
            curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        }
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($curl);
        if (curl_errno($curl)) {
            return 'ERROR ' . curl_error($curl);
        }
        curl_close($curl);
        return json_decode($output, true);
    }

}